/* 
 * File:   SockEndPoint.cpp
 * Author: max
 * 
 * Created on May 22, 2013, 11:05 PM
 */


#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "server_SockEndPoint.h"

using namespace std;
using namespace TP;

#define CONEXION_FINALIZADA 0
#define ERROR_CONEXION -1
#define LONGITUD_BUFFER_DEFECTO 1024
#define SIN_TIME_OUT 1000000

SockEndPoint::SockEndPoint(int &pFileDescriptor, struct sockaddr_in* &pClientAddress){
    _clientAddress = pClientAddress;
    _fileDescriptor = pFileDescriptor;
    _timeOut = SIN_TIME_OUT;
}

SockEndPoint::SockEndPoint(int &pFileDescriptor, struct sockaddr_in* &pClientAddress, const int &pTimeout){
    _clientAddress = pClientAddress;
    _fileDescriptor = pFileDescriptor;
    _timeOut = pTimeout;
}

SockEndPoint::~SockEndPoint() {
    delete _clientAddress;
}

void SockEndPoint::Finalizar (){

    close (_fileDescriptor);
}

void SockEndPoint::Escribir(const uint8_t* pData, const size_t& pLongitudData) throw (Excepcion&){

    try
    {
        this->EscribirBuffer(pData, pLongitudData);
        this->EscribirBuffer(reinterpret_cast <const uint8_t*>(MARCA_FIN_MENSAJE), strlen(MARCA_FIN_MENSAJE));
    }
    catch (Excepcion &ex)
    {
        throw ex;
    }
}

void SockEndPoint::Leer (uint8_t* &pData, uint32_t &pLongitudData) throw (Excepcion&){

    pData = NULL;
    pLongitudData = 0;
    
    uint8_t* buffer = NULL;
    size_t longitudBuffer = 0;
    
    try
    {
        this->inicializarBuffer (buffer, longitudBuffer);

        size_t indiceMensaje = 0;
        pLongitudData = LONGITUD_BUFFER_DEFECTO;
        pData = new uint8_t [LONGITUD_BUFFER_DEFECTO];
        bzero (pData, LONGITUD_BUFFER_DEFECTO); 

        while (0 != bcmp(buffer, reinterpret_cast <const uint8_t*> (MARCA_FIN_MENSAJE), strlen (MARCA_FIN_MENSAJE)))
        {
            if (indiceMensaje == pLongitudData)
            {
                this->redimensionarBuffer (pData, pLongitudData);
            }

            pData[indiceMensaje++] = buffer[0];

            this->desplazarIzquierdaBuffer (buffer, strlen(MARCA_FIN_MENSAJE), 1);

            buffer [strlen(MARCA_FIN_MENSAJE)-1] = this->getByteDesdeSocket();
        }
        
        delete [] buffer;
    }
    catch (Excepcion &ex)
    {
        if (NULL != buffer)
        {
            delete [] buffer;
        }
        
        if (NULL != pData)
        {
            delete [] pData;
        }
        
        throw ex;
    }
}

void SockEndPoint::EscribirBuffer (const uint8_t* pData, const uint32_t pLongitudData) throw (Excepcion&) {
    
    try
    {
        uint32_t intentos = _timeOut;
        int32_t cntBytes = 0;
        uint32_t byteIndex = 0;
        
        while (true)
        {
            cntBytes = send (_fileDescriptor, &(pData[byteIndex]), pLongitudData - byteIndex, 0);
            int errnov = errno;
            
            if (cntBytes > 0)
            {
                byteIndex += cntBytes;
                
                if (pLongitudData == byteIndex)
                    break;
            }
            else if (cntBytes == -1)
            {
                intentos--;
                if (intentos < 0) throw errnov;
            }
        }
    }
    catch (int &ex)
    {
        /* Se informa el problema.
         */
        if (CONEXION_FINALIZADA != ex)
        {
            string msg = "Tiempo de conexion excedido.  Causa: ";
            decodificarError (msg, ex);
            throw Excepcion (msg);
        }
    }    
}

void SockEndPoint::decodificarError (string &pMensaje, int& pCodigoError){
    
    switch (pCodigoError)
    {
        case EACCES:
        pMensaje += "(For UNIX domain sockets, which are identified by pathname) Write permission is denied on the destination socket file, or search permission is denied for one of the directories the path prefix. (See path_resolution(7).).  (For UDP sockets) An attempt was made to send to a network/broadcast address as though it was a unicast address.";
        break;

        case EAGAIN:
        pMensaje += "The socket is marked nonblocking and the requested operation would block. POSIX.1-2001 allows either error to be returned for this case, and does not require these constants to have the same value, so a portable application should check for both possibilities. ";
        break;

        case EBADF:
        pMensaje += "An invalid descriptor was specified.";
        break;

        case ECONNRESET:
        pMensaje += "Connection reset by peer.";
        break;

        case EDESTADDRREQ:
        pMensaje += "The socket is not connection-mode, and no peer address is set.";
        break;

        case EFAULT:
        pMensaje += "An invalid user space address was specified for an argument.";
        break;

        case EINTR:
        pMensaje += "A signal occurred before any data was transmitted; see signal(7).";
        break;

        case EINVAL:
        pMensaje += "Invalid argument passed.";
        break;

        case EISCONN:
        pMensaje += "The connection-mode socket was connected already but a recipient was specified. (Now either this error is returned, or the recipient specification is ignored.) ";
        break;

        case EMSGSIZE:
        pMensaje += "The socket type requires that message be sent atomically, and the size of the message to be sent made this impossible. ";
        break;

        case ENOBUFS:
        pMensaje += "The output queue for a network interface was full. This generally indicates that the interface has stopped sending, but may be caused by transient congestion. (Normally, this does not occur in Linux. Packets are just silently dropped when a device queue overflows.) ";
        break;

        case ENOMEM:
        pMensaje += "No memory available.";
        break;

        case ENOTCONN:
        pMensaje += "The socket is not connected, and no target has been given. ";
        break;

        case ENOTSOCK:
        pMensaje += "The argument sockfd is not a socket. ";
        break;

        case EOPNOTSUPP:
        pMensaje += "Some bit in the flags argument is inappropriate for the socket type. ";
        break;

        case EPIPE:
        pMensaje += "The local end has been shut down on a connection oriented socket. In this case the process will also receive a SIGPIPE unless MSG_NOSIGNAL is set. ";
        break;
    }
}

void SockEndPoint::inicializarBuffer (uint8_t* &pBuffer, size_t &pLongitudBuffer) throw (Excepcion&) {
    
    pBuffer = NULL;
    pLongitudBuffer = 0;
    
    try
    {
        pLongitudBuffer = strlen(MARCA_FIN_MENSAJE);
        pBuffer = new uint8_t [pLongitudBuffer];
        bzero (pBuffer, pLongitudBuffer);
        uint32_t indice = 0;

        while (indice < pLongitudBuffer)
        {
            pBuffer[indice] = this->getByteDesdeSocket();
            indice++;
        }
    }
    catch (Excepcion &ex)
    {
        if (NULL != pBuffer)
        {
            delete [] pBuffer;
            pBuffer = NULL;
            pLongitudBuffer = 0;
        }
        
        throw ex;
    }
}

uint8_t SockEndPoint::getByteDesdeSocket() throw (Excepcion&){
    
    uint8_t byte;
    int bytes_leidos = recv (_fileDescriptor, &byte, sizeof (uint8_t), 0);
    int errnov = errno;
    
    if (bytes_leidos == CONEXION_FINALIZADA)
    {
        this->Finalizar();
        throw Excepcion ("No se puede leer de la conexion.  Conexion finalizada.");
    } 
    else if (bytes_leidos == ERROR_CONEXION)
    {
        this->Finalizar();
        string msg = "No se puede leer de la conexion.";
        decodificarError(msg, errnov);
        throw Excepcion (msg);
    }
    
    return byte;
}

void SockEndPoint::desplazarIzquierdaBuffer (uint8_t* pBuffer, const size_t &pLongitudBuffer, uint32_t pVeces){

    if (pLongitudBuffer == 0) 
    {
        return;
    } 
    else if ( pLongitudBuffer == 1 && pVeces > 0)
    {
        pBuffer [0] = 0;
        return;
    }

    while (pVeces > 0)
    {
        for (size_t i = 0; i < pLongitudBuffer - 1; i++)
        {
            pBuffer[i] = pBuffer[i+1];
        }

        pBuffer [pLongitudBuffer -1] = 0;
        pVeces--;
    }
}

void SockEndPoint::redimensionarBuffer (uint8_t* &pBuffer, size_t &pLongitudBuffer){

    // Me quede sin buffer para el mensaje.  Redimensiono.
    
    size_t longitudBufferAux = pLongitudBuffer + LONGITUD_BUFFER_DEFECTO;
    uint8_t* aux = new uint8_t [longitudBufferAux];

    bzero (aux, longitudBufferAux);
    bcopy(pBuffer, aux, pLongitudBuffer);
    delete [] pBuffer;
    
    pBuffer = aux;
    pLongitudBuffer = longitudBufferAux;
}